﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Drawing;
using System.Xml.Linq;

namespace Djs.Neuronic.Core
{
    /// <summary>
    /// Třída, která obsahuje extenze .NET tříd = rozšíření základní funkcionality
    /// </summary>
    public static class NetExtensions
    {
        #region String
        /// <summary>
        /// <para>
        /// Hledá další výskyt některého ze znaků (searchFor).
        /// Hledá od pozice (index + 1) včetně, směrem ke konci. Znak na pozici (index) tedy neprohledává.
        /// Pokud na vstupu je index == null, pak nehledá a vrací false (index ponechává null).
        /// Pokud najde některý hledaný znak, pak do parametru index vloží jeho index a vrátí true.
        /// Pokud žádný znak nenajde, pak do parametru index vloží null a vrátí false.
        /// </para>
        /// <para>
        /// Metoda je tedy vhodná k postupnému prohledávání jednoho dlouhého stringu.
        /// Před prvním hledáním má být index iniciovaný na hodnotu -1.
        /// </para>
        ///  <para>Pokud this string je null, pak se chová jako by nic nenašel.</para>
        /// </summary>
        /// <param name="text"></param>
        /// <param name="searchFor"></param>
        /// <param name="index"></param>
        /// <returns></returns>
        public static Int32? SearchNext(this string text, char[] searchFor, int index)
        {
            if (text == null || text.Length == 0)
                return null;

            int from = index;
            if (from < 0) from = 0;
            if (from >= text.Length)
                return null;

            int next = text.IndexOfAny(searchFor, from);
            if (next < 0)
                return null;

            return next;
        }
        /// <summary>
        /// Do daného stringu na místa parametrů označených %0, %1, ... %999 vloží zformátované hodnoty dodané jako values.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public static string FormatPercent(this string text, params object[] values)
        {
            return _FormatString(text, i => "%" + i.ToString(), values);
        }
        /// <summary>
        /// Do daného stringu na místa parametrů označených {0}, {1}, ... {999} vloží zformátované hodnoty dodané jako values.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        public static string FormatBracket(this string text, params object[] values)
        {
            return _FormatString(text, i => "{" + i.ToString() + "}", values);
        }
        /// <summary>
        /// Do daného stringu na místa parametrů označených %0, %1, ... %999 vloží zformátované hodnoty dodané jako values.
        /// </summary>
        /// <param name="text"></param>
        /// <param name="values"></param>
        /// <returns></returns>
        private static string _FormatString(string text, Func<int, string> format, object[] values)
        {
            string result = text;
            for (int v = 0; v < values.Length; v++)
            {
                string fmt = format(v);
                if (result.Contains(fmt))
                {
                    string val = (values[v] == null ? "" : values[v].ToString());
                    result = result.Replace(fmt, val);
                }
            }
            return result;
        }
        #endregion
        #region IEnumerable<>
        /// <summary>
        /// Vrátí Dictionary vytvořenou z dodaného soupisu prvků values (hodnoty), a s pomocí funkce (keySelector), která pro danou hodnotu vrátí její klíč.
        /// Pokud by došlo k duplicitě, dojde k běžné systémové chybě.
        /// </summary>
        /// <typeparam name="Key">Typ klíče Dictionary</typeparam>
        /// <typeparam name="Value">Typ dat</typeparam>
        /// <param name="values">Seznam dat</param>
        /// <param name="keySelector">Funkce, která z prvku dat vrátí klíč</param>
        /// <returns></returns>
        public static Dictionary<Key, Value> ToDictionary<Key, Value>(this IEnumerable<Value> values, Func<Value, Key> keySelector)
        {
            return values.ToDictionary(keySelector, false);
        }
        /// <summary>
        /// Vrátí Dictionary vytvořenou z dodaného soupisu prvků values (hodnoty), a s pomocí funkce (keySelector), která pro danou hodnotu vrátí její klíč.
        /// Pokud by došlo k duplicitě, dojde k běžné systémové chybě.
        /// </summary>
        /// <typeparam name="Key">Typ klíče Dictionary</typeparam>
        /// <typeparam name="Value">Typ dat</typeparam>
        /// <param name="values">Seznam dat</param>
        /// <param name="keySelector">Funkce, která z prvku dat vrátí klíč</param>
        /// <param name="skipDuplicity">Přeskočit duplicitní hodnoty? true = přeskočit (v Dictionary zůstane první objekt s daným klíčem).</param>
        /// <returns></returns>
        public static Dictionary<Key, Value> ToDictionary<Key, Value>(this IEnumerable<Value> values, Func<Value, Key> keySelector, bool skipDuplicity)
        {
            Dictionary<Key, Value> result = new Dictionary<Key, Value>();
            foreach (Value value in values)
            {
                Key key = keySelector(value);
                if (!skipDuplicity || !result.ContainsKey(key))
                    result.Add(key, value);
            }
            return result;
        }
        #endregion
        #region Rectangle
        public static Point Center(this Rectangle rectangle)
        {
            return new Point(rectangle.X + rectangle.Width / 2, rectangle.Y + rectangle.Height / 2);
        }
        #endregion
        #region Point
        public static Rectangle FromCenter(this Point center, Size size)
        {
            return new Rectangle(new Point(center.X - size.Width / 2, center.Y - size.Height / 2), size);
        }
        /// <summary>
        /// Vrátí bod result = (this + add)
        /// </summary>
        /// <param name="point"></param>
        /// <param name="add"></param>
        /// <returns></returns>
        public static Point Add(this Point point, Point add)
        {
            return new Point(point.X + add.X, point.Y + add.Y);
        }
        /// <summary>
        /// Vrátí bod result = (this - sub)
        /// </summary>
        /// <param name="point"></param>
        /// <param name="sub"></param>
        /// <returns></returns>
        public static Point Sub(this Point point, Point sub)
        {
            return new Point(point.X - sub.X, point.Y - sub.Y);
        }
        #endregion
        #region Size
        /// <summary>
        /// Zarovná prostor dané velikosti do daného hostitele v daném zarovnání
        /// </summary>
        /// <param name="size"></param>
        /// <param name="bounds"></param>
        /// <param name="alignment"></param>
        /// <returns></returns>
        public static Rectangle AlignTo(this Size size, Rectangle bounds, ContentAlignment alignment, bool cropSize)
        {
            Size realSize = size;
            if (cropSize)
            {
                if (realSize.Width > bounds.Width)
                    realSize.Width = bounds.Width;
                if (realSize.Height > bounds.Height)
                    realSize.Height = bounds.Height;
            }
            return realSize.AlignTo(bounds, alignment);
        }
        /// <summary>
        /// Zarovná prostor dané velikosti do daného hostitele v daném zarovnání
        /// </summary>
        /// <param name="size"></param>
        /// <param name="bounds"></param>
        /// <param name="alignment"></param>
        /// <returns></returns>
        public static Rectangle AlignTo(this Size size, Rectangle bounds, ContentAlignment alignment)
        {
            int x = bounds.X;
            int y = bounds.Y;
            int w = bounds.Width - size.Width;
            int h = bounds.Height - size.Height;
            switch (alignment)
            {
                case ContentAlignment.TopLeft:
                    break;
                case ContentAlignment.TopCenter:
                    x += w / 2;
                    break;
                case ContentAlignment.TopRight:
                    x += w;
                    break;
                case ContentAlignment.MiddleLeft:
                    y += h / 2;
                    break;
                case ContentAlignment.MiddleCenter:
                    x += w / 2;
                    y += h / 2;
                    break;
                case ContentAlignment.MiddleRight:
                    x += w;
                    y += h / 2;
                    break;
                case ContentAlignment.BottomLeft:
                    y += h;
                    break;
                case ContentAlignment.BottomCenter:
                    x += w / 2;
                    y += h;
                    break;
                case ContentAlignment.BottomRight:
                    x += w;
                    y += h;
                    break;
            }
            return new Rectangle(new Point(x, y), size);
        }
        #endregion
        #region Color: Shift
        /// <summary>
        /// Vrací danou barvu s daným posunutím
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="shift">Posunutí barvy</param>
        /// <returns></returns>
        public static Color Shift(this Color root, float shift)
        {
            float r = (float)root.R + shift;
            float g = (float)root.G + shift;
            float b = (float)root.B + shift;
            return GetColor(root.A, r, g, b);
        }
        /// <summary>
        /// Vrací danou barvu s daným posunutím
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="shiftR">Posunutí barvy pro složku R</param>
        /// <param name="shiftG">Posunutí barvy pro složku G</param>
        /// <param name="shiftB">Posunutí barvy pro složku B</param>
        /// <returns></returns>
        public static Color Shift(this Color root, float shiftR, float shiftG, float shiftB)
        {
            float r = (float)root.R + shiftR;
            float g = (float)root.G + shiftG;
            float b = (float)root.B + shiftB;
            return GetColor(root.A, r, g, b);
        }
        /// <summary>
        /// Vrací danou barvu s daným posunutím
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="shiftA">Posunutí barvy pro složku A</param>
        /// <param name="shiftR">Posunutí barvy pro složku R</param>
        /// <param name="shiftG">Posunutí barvy pro složku G</param>
        /// <param name="shiftB">Posunutí barvy pro složku B</param>
        /// <returns></returns>
        public static Color Shift(this Color root, float shiftA, float shiftR, float shiftG, float shiftB)
        {
            float a = (float)root.A + shiftA;
            float r = (float)root.R + shiftR;
            float g = (float)root.G + shiftG;
            float b = (float)root.B + shiftB;
            return GetColor(a, r, g, b);
        }
        /// <summary>
        /// Vrací barvu dle daných složek, přičemž složky (a,r,g,b) omezuje do rozsahu 0 - 255.
        /// </summary>
        /// <param name="a"></param>
        /// <param name="r"></param>
        /// <param name="g"></param>
        /// <param name="b"></param>
        /// <returns></returns>
        private static Color GetColor(float a, float r, float g, float b)
        {
            int ac = (a < 0f ? 0 : (a > 255f ? 255 : (int)a));
            int rc = (r < 0f ? 0 : (r > 255f ? 255 : (int)r));
            int gc = (g < 0f ? 0 : (g > 255f ? 255 : (int)g));
            int bc = (b < 0f ? 0 : (b > 255f ? 255 : (int)b));
            return Color.FromArgb(ac, rc, gc, bc);
        }
        #endregion
        #region Color: Change
        /// <summary>
        /// Změní barvu.
        /// Změna (Change) není posun (Shift): shift přičítá / odečítá hodnotu, ale změna hodnotu mění koeficientem.
        /// Pokud je hodnota složky například 170 a koeficient změny 0.25, pak výsledná hodnota je +25% od výchozí hodnoty směrem k maximu (255): 170 + 0.25 * (255 - 170).
        /// Obdobně změna dolů -70% z hodnoty 170 dá výsledek 170 - 0.7 * (170).
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="change">Změna složek</param>
        /// <returns></returns>
        public static Color ChangeColor(this Color root, float change)
        {
            float r = ChangeCC(root.R, change);
            float g = ChangeCC(root.G, change);
            float b = ChangeCC(root.B, change);
            return GetColor(root.A, r, g, b);
        }
        /// <summary>
        /// Změní barvu.
        /// Změna (Change) není posun (Shift): shift přičítá / odečítá hodnotu, ale změna hodnotu mění koeficientem.
        /// Pokud je hodnota složky například 170 a koeficient změny 0.25, pak výsledná hodnota je +25% od výchozí hodnoty směrem k maximu (255): 170 + 0.25 * (255 - 170).
        /// Obdobně změna dolů -70% z hodnoty 170 dá výsledek 170 - 0.7 * (170).
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="changeR">Změna složky R</param>
        /// <param name="changeG">Změna složky R</param>
        /// <param name="changeB">Změna složky R</param>
        /// <returns></returns>
        public static Color ChangeColor(this Color root, float changeR, float changeG, float changeB)
        {
            float r = ChangeCC(root.R, changeR);
            float g = ChangeCC(root.G, changeG);
            float b = ChangeCC(root.B, changeB);
            return GetColor(root.A, r, g, b);
        }
        /// <summary>
        /// Vrátí složku změněnou koeficientem.
        /// </summary>
        /// <param name="colorComponent"></param>
        /// <param name="change"></param>
        /// <returns></returns>
        private static float ChangeCC(int colorComponent, float change)
        {
            float result = (float)colorComponent;
            if (change > 0f)
            {
                result = result + (change * (255f - result));
            }
            else if (change < 0f)
            {
                result = result - (-change * result);
            }
            return result;
        }
        #endregion
        #region Color: Morph
        /// <summary>
        /// Vrací barvu, která je výsledkem interpolace mezi barvou this a barvou other, 
        /// přičemž od barvy this se liší poměrem morph.
        /// Poměr (morph): 0=vrací se výchozí barva (this).
        /// Poměr (morph): 1=vrací se barva cílová (other).
        /// Poměr může být i větší než 1 (pak je výsledek ještě za cílovou barvou other),
        /// anebo může být záporný (pak výsledkem je barva na opačné straně než je other).
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="other">Cílová barva</param>
        /// <param name="morph">Poměr morph (0=vrátí this, 1=vrátí other, hodnota může být záporná i větší než 1f)</param>
        /// <returns></returns>
        public static Color Morph(this Color root, Color other, float morph)
        {
            float a = GetMorph(root.A, other.A, morph);
            float r = GetMorph(root.R, other.R, morph);
            float g = GetMorph(root.G, other.G, morph);
            float b = GetMorph(root.B, other.B, morph);
            return GetColor(a, r, g, b);
        }
        /// <summary>
        /// Vrátí složku barvy vzniklou morphingem = interpolací.
        /// </summary>
        /// <param name="root">Výchozí složka</param>
        /// <param name="other">Cílová složka</param>
        /// <param name="morph">Poměr morph (0=vrátí this, 1=vrátí other, hodnota může být záporná i větší než 1f)</param>
        /// <returns></returns>
        private static float GetMorph(float root, float other, float morph)
        {
            float dist = other - root;
            return root + morph * dist;
        }
        #endregion
        #region Color: Contrast
        /// <summary>
        /// Vrátí kontrastní barvu černou nebo bílou k barvě this.
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <returns></returns>
        public static Color Contrast(this Color root)
        {
            float a = root.A;
            int q = 184;
            // Určím, kolik složek je světlejších než (q):
            int c = (root.R >= q ? 1 : 0) +
                    (root.G >= q ? 1 : 0) +
                    (root.B >= q ? 1 : 0);
            // Pokud jsou dvě nebo tři složky světlejší než (q), pak vracím černou, jinak bílou:
            return (c >= 2 ? Color.Black : Color.White);
        }

        /// <summary>
        /// Vrátí barvu, která je kontrastní vůči barvě this.
        /// Kontrastní barva leží o dané množství barvy směrem k protilehlé barvě (vždy na opačnou stranu od hodnoty 128), v každé složce zvlášť.
        /// Například ke složce s hodnotou 160 je kontrastní barvou o 32 hodnota (160-32) = 128, k hodnotě 100 o 32 je kontrastní (100+32) = 132.
        /// Tedy kontrastní barva k barvě ((rgb: 64,96,255), contrast=32) je barva: rgb(96,128,223) = (64+32, 96+32, 255-32).
        /// Složka A se nemění.
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="contrast">Míra kontrastu</param>
        /// <returns></returns>
        public static Color Contrast(this Color root, int contrast)
        {
            float a = root.A;
            float r = GetContrast(root.R, contrast);
            float g = GetContrast(root.G, contrast);
            float b = GetContrast(root.B, contrast);
            return GetColor(a, r, g, b);
        }
        /// <summary>
        /// Vrátí barvu, která je kontrastní vůči barvě this.
        /// Kontrastní barva leží o dané množství barvy směrem k protilehlé barvě (vždy na opačnou stranu od hodnoty 128), v každé složce zvlášť.
        /// Například ke složce s hodnotou 160 je kontrastní barvou o 32 hodnota (160-32) = 128, k hodnotě 100 o 32 je kontrastní (100+32) = 132.
        /// Tedy kontrastní barva k barvě ((rgb: 64,96,255), contrast=32) je barva: rgb(96,128,223) = (64+32, 96+32, 255-32).
        /// Složka A se nemění.
        /// </summary>
        /// <param name="root">Výchozí barva</param>
        /// <param name="contrastR">Míra kontrastu ve složce R</param>
        /// <param name="contrastG">Míra kontrastu ve složce G</param>
        /// <param name="contrastB">Míra kontrastu ve složce B</param>
        /// <returns></returns>
        public static Color Contrast(this Color root, int contrastR, int contrastG, int contrastB)
        {
            float a = root.A;
            float r = GetContrast(root.R, contrastR);
            float g = GetContrast(root.G, contrastG);
            float b = GetContrast(root.B, contrastB);
            return GetColor(a, r, g, b);
        }
        /// <summary>
        /// Vrací kontrastní složku
        /// </summary>
        /// <param name="root"></param>
        /// <param name="contrast"></param>
        /// <returns></returns>
        private static float GetContrast(int root, int contrast)
        {
            return (root <= 128 ? root + contrast : root - contrast);
        }
        #endregion
        #region Type
        /// <summary>
        /// Vrátí Namespace.Name daného typu
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static string NamespaceName(this Type type)
        {
            return type.Namespace + "." + type.Name;
        }
        /// <summary>
        /// Vrátí true, pokud aktuální Type implementuje daný interface
        /// </summary>
        /// <param name="type"></param>
        /// <param name="interfaceType"></param>
        /// <returns></returns>
        public static bool ImplementInterface(this Type type, Type interfaceType)
        {
            return type.GetInterfaces().Any(t => (t == interfaceType));
        }
        /// <summary>
        /// Vrátí true, pokud aktuální Type je roven danému předku, nebo je jeho potomek.
        /// Tato varianta nevrací true pro abstraktní typy.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="ancestorType">Typ předka, jehož potomstvo hledáme. Metoda vrátí true i pro předka osobně.</param>
        /// <param name="acceptAbstract">true = vracet i abstraktní typy (implicitně = false = ne)</param>
        /// <returns></returns>
        public static bool IsDescendantOfType(this Type type, Type ancestorType)
        { return type.IsDescendantOfType(ancestorType, false); }
        /// <summary>
        /// Vrátí true, pokud aktuální Type je roven danému předku, nebo je jeho potomek.
        /// Podle hodnoty parametru acceptAbstract může vracet i abstraktní typy.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="ancestorType">Typ předka, jehož potomstvo hledáme. Metoda vrátí true i pro předka osobně.</param>
        /// <param name="acceptAbstract">true = vracet i abstraktní typy (implicitně = false = ne)</param>
        /// <returns></returns>
        public static bool IsDescendantOfType(this Type type, Type ancestorType, bool acceptAbstract)
        {
            if (type == null || (!acceptAbstract && type.IsAbstract)) return false;

            Type testType = type;
            while (testType != null)
            {
                if (testType == ancestorType) return true;
                testType = testType.BaseType;
            }
            return false;
        }
        #endregion
        #region XElement
        public static void AddAttribute(this XElement element, string attributeName, string value)
        {
            element.Add(new XAttribute(attributeName, (value == null ? "" : value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, int value)
        {
            element.Add(new XAttribute(attributeName, Convertor.Int32ToString(value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, decimal value)
        {
            element.Add(new XAttribute(attributeName, Convertor.DecimalToString(value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, bool value)
        {
            element.Add(new XAttribute(attributeName, (value ? "1" : "0")));
        }
        public static void AddAttribute(this XElement element, string attributeName, DateTime value)
        {
            element.Add(new XAttribute(attributeName, Convertor.DateTimeToString(value)));
        }
        public static void AddAttribute(this XElement element, string attributeName, Rectangle value)
        {
            element.Add(new XAttribute(attributeName, Convertor.RectangleToString(value)));
        }
        public static string GetAttributeString(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return null;
            return attr.Value;
        }
        public static Int32 GetAttributeInt32(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return 0;
            return (int)Convertor.StringToInt32(attr.Value);
        }
        public static Int32? GetAttributeInt32N(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return null;
            return (Int32?)Convertor.StringToInt32N(attr.Value);
        }
        public static decimal GetAttributeDecimal(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return 0m;
            return (decimal)Convertor.StringToDecimal(attr.Value);
        }
        public static bool GetAttributeBoolean(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return false;
            return (attr.Value == "1" || attr.Value.ToUpper() == "Y" || attr.Value.ToLower() == "true" || attr.Value.ToLower() == "yes" || attr.Value.ToUpper() == "A");
        }
        public static DateTime GetAttributeDateTime(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return DateTime.MinValue;
            return (DateTime)Convertor.StringToDateTime(attr.Value);
        }
        public static Rectangle GetAttributeRectangle(this XElement element, string attributeName)
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr == null) return Rectangle.Empty;
            return (Rectangle)Convertor.StringToRectangle(attr.Value);
        }
        public static T GetAttributeEnum<T>(this XElement element, string attributeName) where T : struct
        {
            T defaultValue = (T)(Enum.GetValues(typeof(T)).GetValue(0));
            return GetAttributeEnum<T>(element, attributeName, defaultValue);
        }
        public static T GetAttributeEnum<T>(this XElement element, string attributeName, T defaultValue) where T : struct
        {
            XAttribute attr = GetXAttribute(element, attributeName);
            if (attr != null)
            {
                T result;
                if (Enum.TryParse<T>(attr.Value, true, out result))
                    return result;
            }
            return defaultValue;
        }

        private static XAttribute GetXAttribute(XElement element, string attributeName)
        {
            if (element == null) return null;
            XAttribute attr = element.Attribute(attributeName);
            return attr;
        }
        public static void Save(this XDocument xDoc, string file, bool textFormat)
        {
            System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings();
            settings.ConformanceLevel = System.Xml.ConformanceLevel.Document;
            settings.Encoding = Encoding.UTF8;
            settings.Indent = textFormat;
            settings.IndentChars = "  ";
            settings.NewLineHandling = System.Xml.NewLineHandling.Entitize;
            settings.NewLineChars = Environment.NewLine;
            settings.NewLineOnAttributes = false;
            settings.OmitXmlDeclaration = false;
            using (System.Xml.XmlWriter writter = System.Xml.XmlWriter.Create(file, settings))
            {
                xDoc.Save(writter);
            }
        }
        #endregion
    }
}
